Unit BObjectUnit;

{$mode objfpc}{$H+}{$interfaces CORBA}

Interface

Uses
  {$IFDEF TestCase}
    BMockQueryUnit,
  {$ELSE}
    BQueryUnit,
  {$ENDIF}
  Classes, SysUtils, BCommonUnit, BListsUnit;

Const
  DEFAULT_ID = -1;

Type BORMStateKind = (
  ormUnknow = 0,
  ormCommitted = 1,
  ormChanged = 2,
  ormAdded = 3,
  ormDeleted = 4
);

Type

{ IGenericInterface }

  Generic IGenericInterface<BManagedClass> = Interface
    Procedure Load(Const aObject: BManagedClass);
    Function GetID: Integer;
    Function GetORMState: BORMStateKind;
    {$IFDEF TestCase}Procedure SetORMState(Const aState: BORMStateKind);{$ENDIF}
    Procedure Validate(Const aCommit: Boolean = FALSE);
    Function Save(Const aQuery: BQueryClass): Boolean;
    Procedure InitializeObjects;
End;

Type

{ BObjectClass }

BObjectClass = Class
  Private
  Protected
    bInitialized: Boolean;
    bORMState: BORMStateKind;
    bDeleted: Boolean;
    bID: Integer;
    bClassID: Integer;
    bRefCount: LongInt;
    Function GetID: Integer;
    Function GetORMState: BORMStateKind;
    {$IFDEF TestCase}Procedure SetORMState(Const aORMState: BORMStateKind);{$ENDIF}
  Public
    Property ID: Integer Read bID;
    Property ClassID: Integer Read bClassID Default 10;
    Property ORMState: BORMStateKind Read bORMState;
    Property Deleted: Boolean Read bDeleted;

    Function Equals(aObject: BObjectClass): Boolean; Virtual; Reintroduce;

    Constructor Build(Const aID: Integer); Virtual;
    Destructor Burn; Virtual; Abstract;
End;

Type BObjectsList = Specialize BList<BObjectClass>;
Type BObjectsThreadList = Specialize BThreadList<BObjectClass>;

Type BManagerState = (msUnknow = 0, msNormal = 1, msLoading = 2);

Type

{ BManagerGeneric }

Generic BManagerGeneric<BManagedClass> = Class
  Type BCloneEvent = Procedure(Const aOriginal,aClone: BManagedClass) Of Object;
  Type IManagedInterface = Specialize IGenericInterface<BManagedClass>;
  Type BManagedList = Specialize BList<BManagedClass>;
  Type BManagedThreadList = Specialize BThreadList<BManagedClass>;

  Private
    bModified: Boolean;
    bOnAssign: BCloneEvent;
    bOnClone: BCloneEvent;
    bState: BManagerState;
    bDBIndex: Integer;
    bTableName: String;
    Function CheckAdd(Const aObject: BManagedClass): Boolean;
  Protected

    bCommittedIndex: Integer;
    bNonCommittedIndex: Integer;

    bCommitted: BManagedThreadList;
    bNonCommitted: BManagedThreadList;

    Procedure Validate(Const aNonCommitted, aCommitted: BManagedList);
    Function PrepareCommit(Const aQuery: BQueryClass;
      Const aNonCommitted, aCommitted: BManagedList): Boolean;
    Procedure UpplyCommit(Const aNonCommitted, aCommitted: BManagedList);

    Function CloneObject(Const aOriginal: BManagedClass): BManagedClass;

    {$ifndef cpux86_64}
    Procedure EnterState(Const aState: BManagerState); Virtual;
    Procedure LeaveState; Virtual;
    {$endif}

    Procedure OnCloneObject(Const aOriginal, aClone: BManagedClass);
    Procedure OnAssignObject(Const aOriginal, aClone: BManagedClass);
  Public
    Property DBIndex: Integer Read bDBIndex;
    Property TableName: String Read bTableName;

    Property State: BManagerState Read bState;
    Property Committed: BManagedThreadList Read bCommitted;
    Property NonCommitted: BManagedThreadList Read bNonCommitted;
    Property Modified: Boolean Read bModified;

    Property OnClone: BCloneEvent Read bOnClone Write bOnClone;
    Property OnAssign: BCloneEvent Read bOnAssign Write bOnAssign;
    //Property OnCommit: BCommitEvent Read bOnCommit Write bOnCommit;
    //Property OnSave: BSaveEvent Read bOnSave Write bOnSave;

    {$ifdef cpux86_64}
    Procedure EnterState(Const aState: BManagerState); Virtual;
    Procedure LeaveState; Virtual;
    {$endif}

    Function AddObject(Const aID: Integer = DEFAULT_ID): BManagedClass;
    Function GetObject(Const aID: Integer): BManagedClass; Virtual;
    Function ChangeObject(Const aObject: BManagedClass): BManagedClass;
    Function DeleteObject(Var aObject: BManagedClass): BManagedClass;

    //**
    Procedure FillSpecificList(Const aIntegers: BIntegersList;
      Var aOutput: BManagedList; Const aSafe: Boolean = TRUE);

    Procedure Commit; Virtual;
    Procedure Rollback;
    Procedure Purge;

    Constructor Build(Const aCommited: BManagedThreadList;
      Const aDBIndex: Integer; Const aTableName: String);
    Destructor Burn; Virtual;
End;

Implementation

{ BObjectClass }

Function BObjectClass.GetID: Integer;
Begin
  Result := bID;
End;

Function BObjectClass.GetORMState: BORMStateKind;
Begin
  Result := bORMState;
End;

{$IFDEF TestCase}
Procedure BObjectClass.SetORMState(Const aORMState: BORMStateKind);
Begin
  bORMState := aORMState;
End;
{$ENDIF}

Function BObjectClass.Equals(aObject: BObjectClass): Boolean;
Begin
  Result := ID = aObject.ID;
End;

Constructor BObjectClass.Build(Const aID: Integer);
Begin
  bInitialized := FALSE;
  bID := aID;
End;

{ BManagerGeneric }

Function BManagerGeneric.CheckAdd(Const aObject: BManagedClass): Boolean;
Begin
  Result := Not((aObject = nil) Or Not(aObject.ID = DEFAULT_ID) Or
    aObject.Deleted);
End;

Procedure BManagerGeneric.Validate(
  Const aNonCommitted, aCommitted: BManagedList);
Var
  i: Integer;
  aObject: BManagedClass;
Begin
  For i := 0 To aNonCommitted.Count - 1 Do
    Begin
      aObject := aNonCommitted[i];
      IManagedInterface(aObject).Validate(TRUE);
    End;
End;

Function BManagerGeneric.PrepareCommit(Const aQuery: BQueryClass;
  Const aNonCommitted, aCommitted: BManagedList): Boolean;
Var
  i: Integer;
  aObject: BManagedClass;
Begin
  Result := TRUE;

  For i := 0 To aNonCommitted.Count - 1 Do
    Begin
      aObject := aNonCommitted[i];
      Result := Result And aObject.Save(aQuery);
      If Not(Result) Then
        Raise BExceptionClass.Build(aObject.ID, 'Unseccessful attempt to save');
    End;
End;

Procedure BManagerGeneric.UpplyCommit(
  Const aNonCommitted, aCommitted: BManagedList);
Var
  i: Integer;
  aObject, aCommittedObject: BManagedClass;
Begin
  // Apply changes of NonCommited Items to Managed Items
  For i := 0 To aNonCommitted.Count - 1 Do
    Begin
      aObject := aNonCommitted.Items[i];
      Case Integer((TObject(aNonCommitted[i]) As IManagedInterface).GetORMState) Of
        Integer(ormAdded):
          Begin
            aObject := CloneObject(aObject);
            aObject.bORMState := ormCommitted;
            aCommitted.Add(aObject);
            aObject := nil;
          End;
        Integer(ormChanged), Integer(ormDeleted):
          Begin
            aCommittedObject := aCommitted.Find(aObject.ID);
            // aCommittedObject will be nil when changed or deleted
            // noncommitted object
            If Not(aCommittedObject = nil) Then
              Begin
                OnAssign(aCommitted.Find(aObject.ID), aObject);
                aCommitted.Find(aObject.ID).Load(aObject);
              End;
          End;
        Else
          Raise BExceptionClass.Build('Illegal %s ORMState at %d',
            [ClassName, (TObject(aNonCommitted[i]) As IManagedInterface).GetID]);
      End;
    End;
  bModified := FALSE;
End;

Function BManagerGeneric.CloneObject(
  Const aOriginal: BManagedClass): BManagedClass;
Begin
  If aOriginal = nil Then
    Raise BExceptionClass.Build('Illegal original for clone');
  Result := BManagedClass.Build(aOriginal.ID);
  If Assigned(bOnClone) Then bOnClone(aOriginal, Result);
  OnCloneObject(aOriginal, Result);
  Result.Load(aOriginal);
  Result.bORMState := ormChanged;
End;

Procedure BManagerGeneric.EnterState(Const aState: BManagerState);
Begin
  bState := aState;
End;

Procedure BManagerGeneric.LeaveState;
Begin
  bState := msNormal;
End;

Procedure BManagerGeneric.OnCloneObject(Const aOriginal, aClone: BManagedClass);
Begin
  // Original is nil when deleted noncommitted object
  If Not((aOriginal = nil) Or (aClone = nil)) Then
    aClone.bDeleted := aOriginal.Deleted;
End;

Procedure BManagerGeneric.OnAssignObject(Const aOriginal,aClone: BManagedClass);
Begin
  // Original is nil when deleted noncommitted object
  If Not((aOriginal = nil) Or (aClone = nil)) Then
    aOriginal.bDeleted := aClone.Deleted;
End;

Function BManagerGeneric.GetObject(Const aID: Integer): BManagedClass;
Begin
  Result := bNonCommitted.LockList.Find(aID);
  bNonCommitted.UnlockList;
  If Result = nil Then
    Begin
      Result := bCommitted.LockList.Find(aID);
      bCommitted.UnlockList;
    End;
  If Not(Result = nil) And Result.Deleted Then
    Result := nil;
End;

Function BManagerGeneric.AddObject(Const aID: Integer): BManagedClass;
Begin
  Result := BManagedClass.Build(aID);
  IManagedInterface(Result).InitializeObjects;
  Case bState Of
    msLoading:
      Begin
        Result.bORMState := ormCommitted;
        bCommitted.Add(Result);
      End;
    msNormal:
      Begin
        If Not(CheckAdd(Result)) Then Exit;
        Dec(bNonCommittedIndex);
        Result.bID := bNonCommittedIndex;
        Result.bORMState := ormAdded;
        bNonCommitted.Add(Result);
        bModified := TRUE;
      End;
    Else Raise Exception.Create('Illegal state of Manager');
  End;
End;

Function BManagerGeneric.ChangeObject(Const aObject: BManagedClass): BManagedClass;
Var
  aNonCommitted: BManagedList;
Begin
 // Check for nil/ID and is the object requested for change/deleted
  Result := nil;
 If (aObject = nil) Or (aObject.ID = -1) Or
   aObject.Deleted Then
   Raise BExceptionClass.Build(51, 'Try to change illegal object');

 If Not(BManagedClass(aObject).bORMState = ormCommitted) Then
    Result := aObject
  Else
   Begin
     Try
       aNonCommitted := bNonCommitted.LockList;
       If aNonCommitted.Present(aObject) Then
         Result := aNonCommitted.Find(aObject.ID)
       Else
         Begin
           Result := CloneObject(aObject);
           bNonCommitted.Add(Result);
         End;
     Finally
       bNonCommitted.UnlockList;
     End;
     bModified := TRUE;
   End;
End;

Function BManagerGeneric.DeleteObject(Var aObject: BManagedClass):BManagedClass;
Begin
  Result := nil;
  aObject := ChangeObject(aObject);
  aObject.bDeleted := TRUE;
  aObject.bORMState := ormDeleted;
  Result := aObject;
End;

Procedure BManagerGeneric.FillSpecificList(Const aIntegers: BIntegersList;
  Var aOutput: BManagedList; Const aSafe: Boolean);
Var
  i: Integer;
  aObject: BManagedClass;
Begin
  If (aOutput = nil) Or (aIntegers = nil) Then
    If aSafe Then Exit
    Else Raise BExceptionClass.Build(5, 'Get nil List');
  For i := 0 To aIntegers.Count - 1 Do
    Begin
      aObject := GetObject(aIntegers[i]);
      If Not(aObject = nil) Then
        aOutput.Add(aObject)
      Else
        If aSafe Then Continue
        Else Raise BExceptionClass.Build(2, 'Attempt of nil insertion');
    End;
End;

Procedure BManagerGeneric.Commit;
Var
  aQuery: BQueryClass;
  aCommitted, aNonCommitted: BManagedList;
Begin
  If Not(bModified) Then Exit;

  aQuery := BQueryClass.Build(bDBIndex);
  aCommitted := bCommitted.LockList;
  aNonCommitted := bNonCommitted.LockList;

  // Attempt to process all NonCommited Objects
  Try

    Validate(aNonCommitted, aCommitted);
    PrepareCommit(aQuery, aNonCommitted, aCommitted);
    UpplyCommit(aNonCommitted, aCommitted);
    aQuery.Go;
  Finally
    aQuery.Burn;
    bCommitted.UnlockList;
    bNonCommitted.UnlockList;
  End;
End;

Procedure BManagerGeneric.Rollback;
Begin
  bNonCommitted.Purge;
  bNonCommittedIndex := -1;
End;

Procedure BManagerGeneric.Purge;
Begin
  bCommitted.Purge;
  Rollback;
End;

Constructor BManagerGeneric.Build(Const aCommited: BManagedThreadList;
  Const aDBIndex: Integer; Const aTableName: String);
Begin
  bDBIndex := aDBIndex;
  bTableName := aTableName;

  OnAssign := @OnAssignObject;
  bState := msNormal;

  bCommitted := aCommited;
  bNonCommitted := BManagedThreadList.Build;

  bNonCommittedIndex := -1;
End;

Destructor BManagerGeneric.Burn;
Begin
  bNonCommitted.Purge;
  bNonCommitted.Burn;
End;

End.
